package btp

import (
	"encoding/binary"
	"fmt"
)

type Field struct {
	info *fieldInfo
	data []byte
}

func (f *Field) Bytes() []byte { return f.data }

// func (f *Field) Id() byte        { return f.info.Id() }
// func (f *Field) DType() DataType { return f.info.dtype }
func (f *Field) Name() string { return f.info.name }

type ErrWrongDataLength struct {
	Field  string
	Expect int
	Got    int
}

func (e *ErrWrongDataLength) Error() string {
	return fmt.Sprintf("wrong data length: %s expect %d but got %d", e.Field, e.Expect, e.Got)
}

func (f *Field) Uint8() (uint8, error) {
	if f.info.dtype != DUint8 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 1 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 1, Got: len(f.data)}
	}
	return f.data[0], nil
}

func (f *Field) Int8() (int8, error) {
	if f.info.dtype != DInt8 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 1 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 1, Got: len(f.data)}
	}
	return int8(f.data[0]), nil
}

func (f *Field) Uint16() (uint16, error) {
	if f.info.dtype != DUint16 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 2 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 2, Got: len(f.data)}
	}
	return binary.BigEndian.Uint16(f.data), nil
}

func (f *Field) Int16() (int16, error) {
	if f.info.dtype != DInt16 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 2 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 2, Got: len(f.data)}
	}
	return int16(binary.BigEndian.Uint16(f.data)), nil
}

func (f *Field) Uint32() (uint32, error) {
	if f.info.dtype != DUint32 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 4 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 4, Got: len(f.data)}
	}
	return binary.BigEndian.Uint32(f.data), nil
}

func (f *Field) Int32() (int32, error) {
	if f.info.dtype != DInt32 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 4 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 4, Got: len(f.data)}
	}
	return int32(binary.BigEndian.Uint32(f.data)), nil
}

func (f *Field) Uint64() (uint64, error) {
	if f.info.dtype != DUint64 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 8 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 8, Got: len(f.data)}
	}
	return binary.BigEndian.Uint64(f.data), nil
}

func (f *Field) Int64() (int64, error) {
	if f.info.dtype != DInt64 {
		return 0, &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	if len(f.data) != 8 {
		return 0, &ErrWrongDataLength{Field: f.info.name, Expect: 8, Got: len(f.data)}
	}
	return int64(binary.BigEndian.Uint64(f.data)), nil
}

func (f *Field) String() (string, error) {
	if f.info.dtype != DString {
		return "", &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	return string(f.data), nil
}

func (f *Field) BigString() (string, error) {
	if f.info.dtype != DBigString {
		return "", &ErrWrongDataType{Field: f.info.name, DataType: f.info.dtype}
	}
	return string(f.data), nil
}

func (f *Field) encodeSize() int {
	if f.info.dtype == DString {
		return len(f.data) + 2
	}
	if f.info.dtype == DBigString {
		return len(f.data) + 3
	}
	return len(f.data) + 1
}

func (f *Field) encode() []byte {
	if f.info.dtype == DString {
		return append([]byte{f.info.id, byte(len(f.data))}, f.data...)
	}
	if f.info.dtype == DBigString {
		b := make([]byte, 2)
		binary.BigEndian.PutUint16(b, uint16(len(f.data)))
		return append([]byte{f.info.id, b[0], b[1]}, f.data...)
	}
	return append([]byte{f.info.id}, f.data...)
}

//

type parser struct {
	i int
	b []byte
}

func (p *parser) remain() int {
	return len(p.b) - p.i
}

func (p *parser) readField() (*Field, error) {
	fid := p.b[p.i]
	p.i += 1
	f := &Field{info: getFieldById(fid)}
	if f.info == nil {
		return nil, fmt.Errorf("unknown field: %d", fid)
	}
	var err error
	switch f.info.dtype {
	case DUint8, DInt8:
		f.data, err = p.read(1)
	case DUint16, DInt16:
		f.data, err = p.read(2)
	case DUint32, DInt32:
		f.data, err = p.read(4)
	case DUint64, DInt64:
		f.data, err = p.read(8)
	case DString:
		f.data, err = p.readSized()
	case DBigString:
		f.data, err = p.readSized2()
	default:
		return nil, fmt.Errorf("unknown datatype: %d", f.info.dtype)
	}
	return f, err
}

func (p *parser) read(size int) ([]byte, error) {
	if p.remain() < size {
		return nil, fmt.Errorf("no bytes to read")
	}
	b := p.b[p.i : p.i+size]
	p.i += size
	return b, nil
}

func (p *parser) readSized() ([]byte, error) {
	size, err := p.read(1)
	if err != nil {
		return nil, err
	}
	return p.read(int(size[0]))
}

func (p *parser) readSized2() ([]byte, error) {
	bsize, err := p.read(2)
	if err != nil {
		return nil, err
	}
	size := binary.BigEndian.Uint16(bsize)
	return p.read(int(size))
}
